<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        :root {
            --color1: #ffffff;
            --color2: #d9d9d9;
            --flip-duration: .3s;
        }

        .digit-flipper {
            display: inline-block;
            height: 0.98em;
            font-size: 20vmin;
            line-height: 1;
            margin: 0 0.02em;
            -webkit-perspective: 300px;
            perspective: 300px;
            position: relative;
            width: 0.65em;
        }

        .digit-flipper__digit {
            display: block;
            height: 100%;
            position: absolute;
            text-align: center;
            width: 100%;
        }

        .digit-flipper__digit-top,
        .digit-flipper__digit-bottom {
            color: black;
            display: block;
            height: 100%;
            position: absolute;
            -webkit-transform-origin: 50% 50%;
            transform-origin: 50% 50%;
            width: 100%;
        }

        .digit-flipper__digit-top {
            background-color: var(--color1);
            border-top-left-radius: 10px;
            border-top-right-radius: 10px;
            -webkit-clip-path: inset(0 0 51% 0);
            clip-path: inset(0 0 51% 0);
            overflow: hidden;
            -webkit-transform: rotateX(0deg);
            transform: rotateX(0deg);
        }

        .digit-flipper__digit-bottom {
            background-color: var(--color2);
            border-bottom-left-radius: 10px;
            border-bottom-right-radius: 10px;
            -webkit-clip-path: inset(51% 0 0 0);
            clip-path: inset(51% 0 0 0);
            -webkit-transform: rotateX(90deg);
            transform: rotateX(90deg);
        }

        .digit-flipper__digit--flip-bottom .digit-flipper__digit-bottom {
            -webkit-animation: flip-bottom var(--flip-duration) ease-in 0s 1 forwards;
            animation: flip-bottom var(--flip-duration) ease-in 0s 1 forwards;
        }

        .digit-flipper__digit--flip-top .digit-flipper__digit-top {
            -webkit-animation: flip-top var(--flip-duration) ease-in 0s 1 forwards;
            animation: flip-top var(--flip-duration) ease-in 0s 1 forwards;
        }

        .digit-flipper__digit--flip-done .digit-flipper__digit-bottom {
            -webkit-transform: rotateX(0deg);
            transform: rotateX(0deg);
        }

        @-webkit-keyframes flip-top {
            from {
                -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
            }

            to {
                -webkit-transform: rotateX(-90deg);
                transform: rotateX(-90deg);
            }
        }

        @keyframes flip-top {
            from {
                -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
            }

            to {
                -webkit-transform: rotateX(-90deg);
                transform: rotateX(-90deg);
            }
        }

        @-webkit-keyframes flip-bottom {
            from {
                -webkit-transform: rotateX(90deg);
                transform: rotateX(90deg);
            }

            to {
                -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
            }
        }

        @keyframes flip-bottom {
            from {
                -webkit-transform: rotateX(90deg);
                transform: rotateX(90deg);
            }

            to {
                -webkit-transform: rotateX(0deg);
                transform: rotateX(0deg);
            }
        }

        body {
            -webkit-box-align: center;
            align-items: center;
            background-color: #f6f6f6;
            background-image: url(" ");
            display: -webkit-box;
            display: flex;
            height: 100vh;
            -webkit-box-pack: center;
            justify-content: center;
            -webkit-box-orient: vertical;
            -webkit-box-direction: normal;
            flex-direction: column;
        }

        .controls {
            background-color: white;
            border-radius: 10px;
            box-shadow: inset -2px -2px 5px 0px rgba(0, 0, 0, 0.1), 5px 5px 10px 0px rgba(0, 0, 0, 0.15);
            margin: 20px 0 auto;
            padding: 4px 20px 20px;
            text-align: center;
        }

        .cool-element {
            background-color: #404040;
            border-radius: 15px;
            box-shadow: inset -2px -2px 10px 0px black, inset 4px 4px 10px 0px rgba(255, 255, 255, 0.4), 5px 5px 15px 0px rgba(0, 0, 0, 0.3);
            margin: -40px 0 auto;
            padding: 20px 18px 16px;
        }
    </style>
</head>

<body>
    <div class="controls">
        <p>输入一个数字，然后按“创建计数器”</p>
        <input class="number-input" type="number" value="567" />
        <button class="number-button">创建计数器</button>
    </div>
    <div class="cool-element"></div>

    <script>
        const element = document.querySelector(".cool-element"),
            numberInput = document.querySelector(".number-input"),
            numberSubmit = document.querySelector(".number-button");

        const ROOT_CLASS_NAME = "digit-flipper";

        class DigitFlipper {
            constructor(element, options = {
                number: 9,
                iterationCount: 9
            }) {

                // First, some parameter sanitizing:
                if (options.number > 9 || options.number < 0) return;

                this.options = Object.assign({}, options);

                if (!this.options.number) this.options.number = 9;
                if (!this.options.iterationCount) this.options.iterationCount = 9;

                // Adjusting the number of iterations,
                // in case our numbers end up in the negatives:
                if (this.options.number - this.options.iterationCount < 0) {
                    this.options.iterationCount = this.options.number;
                }

                this.element = element;
                this.digitClassName = `${ROOT_CLASS_NAME}__digit`;
                this.topClassName = `${this.digitClassName}-top`;
                this.bottomClassName = `${this.digitClassName}-bottom`;
                this.flipTopClass = `${this.digitClassName}--flip-top`;
                this.flipBottomClass = `${this.digitClassName}--flip-bottom`;
                this.flipDoneClass = `${this.digitClassName}--flip-done`;
                this.DOMNodes = [];
                this.flipDuration = parseFloat(
                    (window.getComputedStyle(document.documentElement).
                        getPropertyValue("--flip-duration") || "1s").
                        replace("s", ""));

                this._init();

                return this;
            }

            _init() {
                this._populateDOM();
            }

            // creates DOM elements for each digit and all of its "iterations"
            _populateDOM() {
                let i = this.options.number - this.options.iterationCount;
                for (i; i <= this.options.number; i++) {
                    const digit = document.createElement("span"),
                        digitTop = document.createElement("span"),
                        digitBottom = document.createElement("span"),
                        digitText = document.createTextNode(i);

                    digit.className = this.digitClassName;
                    digitTop.className = this.topClassName;
                    digitBottom.className = this.bottomClassName;

                    digitTop.appendChild(digitText);
                    digitBottom.appendChild(digitText.cloneNode());
                    digit.appendChild(digitTop);
                    digit.appendChild(digitBottom);

                    this.DOMNodes.push(digit);
                    this.element.insertAdjacentElement("afterbegin", digit);
                }
            }

            // runs the animtion sequence for the digit
            flip() {
                this.DOMNodes.forEach((node, index) => {
                    const nextNode = this.DOMNodes[index + 1];
                    let delay = this.flipDuration * index * 1000;

                    // The flipBottomClass turns the bottom half
                    // down from it's inital state of 90deg

                    // The flipTopClass turns the top half
                    // down from it's inital state of 0deg

                    const t1 = setTimeout(() => {
                        node.classList.add(this.flipBottomClass);
                        clearTimeout(t1);
                        const t2 = setTimeout(() => {
                            if (nextNode) node.classList.add(this.flipTopClass);
                            clearTimeout(t2);
                            const t3 = setTimeout(() => {
                                node.style.zIndex = index + 1;
                                clearTimeout(t3);
                            }, this.flipDuration);
                        }, this.flipDuration);
                    }, delay);
                });
            }
        }


        class FlipCounter {
            constructor(element, value) {
                if (typeof value !== "number") return;

                this.element = element;
                this.targetNumber = value;
                this.targetDigits = [];
                this.numDigits = this.targetNumber.toString().length;
                this.DOMNodes = [];
                this.flipperInstances = [];

                // separate the digits of the value arg
                for (let i = 0; i < this.numDigits; i++) {
                    this.targetDigits.push(this.targetNumber.toString()[i]);
                }

                this.populateDOM();
                this.populateInstanceArray();
            }

            // creates wrapper elements for each digit
            populateDOM() {
                this.element.innerHTML = "";

                let i = 0;
                for (i; i < this.numDigits; i++) {
                    const container = document.createElement("span");
                    container.className = ROOT_CLASS_NAME;

                    this.element.appendChild(container);
                    this.DOMNodes.push(container);
                }
            }

            // instantiate a DigitFlipper object for each digit
            populateInstanceArray() {
                this.DOMNodes.forEach((digit, index) => {
                    this.flipperInstances.push(
                        new DigitFlipper(digit, {
                            number: this.targetDigits[index],
                            iterationCount: 4
                        }));


                });
            }

            // runs the animation, with a 200ms stagger
            play() {
                this.flipperInstances.forEach((instance, index) => {
                    let delay = index * 200;
                    setTimeout(() => instance.flip(), delay);
                });
            }
        }


        // Handles the input field, for the demo
        const onClick = () => {
            let num = Number(numberInput.value);
            if (num >= 0) {
                let counter = new FlipCounter(element, num);
                counter.play();
            }
        };

        numberSubmit.addEventListener("click", onClick);

        // kick off the initial one
        const counter = new FlipCounter(element, Number(numberInput.value));
        counter.play();
    </script>
</body>

</html>